package Proofs;

import java.io.IOException;

import utils.ByteTree;
import crypto.*;
import ArithmeticObjects.*;

/**
 * This class is the general class from which all proof classes extend.  It contains the constructor for
 * all three proofs, and functions used by all proofs.
 * @author Oded Raphael
 * @author Etai Plushnick
 */
public class Proof {

	protected RandomOracle ROs;
	protected RandomOracle ROv;

	/** Creates an instance of Proof.
	 * Input description is as follows:
	 * ROs - Random oracle used to compute seeds
	 * ROv - Random oracle used to compute challenges
	 */
	public Proof(RandomOracle ROs, RandomOracle ROv) {
		this.ROs = ROs;
		this.ROv = ROv;
	}
	
	/** Computes a random array of elements from the given seed. 
	 * Input description is as follows:
	 * seed - the seed set in the prg.
	 * bit_length - bit length of the prime of the modular group used throughtout the algorithm.
	 * prg - pseudo-random generator used to compute the random array.
	 * exp1 - first exponent used in the computation of the random array.
	 * exp2 - second exponent used in the computation of the random array (set to -1 if not needed).
	 * n - modulus.
	 */
	public Element[] computeRandomArray(byte[] seed, int bit_length, PRG prg, int N0, Element exp1, Element exp2, Element n) throws Exception {
		int length = (int) Math.ceil((double)bit_length / 8); 
		prg.setSeed(seed);
		byte[] byteArr = prg.runPRG(length*N0);
		LargeNumber t;
		int position = 0;
		byte[] temp = new byte[length];
		Element[] randomArray = new Element[N0];
		for (int i=0; i<N0; i++) {
			System.arraycopy(byteArr, position, temp, 0, length);
			temp[0] &=  (0xFF >> (8 - (bit_length % 8)));
			position += length;
			t = new LargeNumber(temp);
			t = t.mod((LargeNumber.TWO.power(exp1.getValue())));
			if (exp2.compareTo(new LargeNumberObject(0)) > 0){ //if exp2 != -1, perform randomArray[i] = (t^exp2) mod n
				LargeNumber a = t.modPow(exp2.getValue(), n.getValue());
				randomArray[i] = new LargeNumberObject(a);
			}
			else { //if exp2 == -1, perform randomArray[i] = t mod 2^n
				LargeNumber a = t.mod(LargeNumber.TWO.power((n.getValue())));;
				randomArray[i]= new LargeNumberObject(a);
			}
		}
		return randomArray;
	}
	
	/** Returns a seed equal to ROs(rho | leaf("generators")).
	 * Input description is as follows:
	 * rho - prefix to random oracle.
	 */
	public byte[] computeSeed(byte[] rho) throws IOException {  
		ByteTree node= ByteTree.CreateLeaf("generators");
		return ROs.runRandomOracle(concatenate(rho, node));
	}

	protected byte[] concatenate(byte[] rho, ByteTree node) {
		/* Returns the byte array representation of the concatenation of rho and node */
		byte[] rhoNode=new byte[rho.length+node.getRawByteTree().length];
		System.arraycopy(rho, 0, rhoNode, 0, rho.length);
		System.arraycopy(node.getRawByteTree(), 0, rhoNode, rho.length, node.getRawByteTree().length);
		return rhoNode;
	}

	protected Element computeChallenge(byte[] rho, byte[] seed, ByteTree tau, int nv) throws Exception {
		/* Computes a challenge, equal to ROv(rho | node(leaf(seed), tau)). */
		
		/* Creation of the node concatenated with rho for the computation of v: */
		ByteTree node;
		node=ByteTree.CreateNode();
		node.AddMultiChildsToNode(ByteTree.CreateLeaf(seed), tau);
		
		/* Computation of v: */
		byte[] tempRes = ROv.runRandomOracle(concatenate(rho, node));
		LargeNumber v=new LargeNumber(tempRes);
		v=v.mod((LargeNumber.TWO.power(new LargeNumber(nv))));
		return new LargeNumberObject(v);
	}
}